 1.ClickHouse表引擎之日志
   
   表引擎（即表的类型）决定了：
   1).数据的存储方式和位置，写到哪里以及从哪里读取数据
   2).支持哪些查询以及如何支持。
   3).并发数据访问。
   4).索引的使用（如果存在）。
   5).是否可以执行多线程请求。
   6).数据复制参数。
   ClickHouse的表引擎有很多，下面介绍其中几种，对其他引擎有兴趣的可以去查阅官方文档：https://clickhouse.ya
ndex/docs/zh/operations/table_engines/
   
   1).TinyLog
   最简单的表引擎，用于将数据存储在磁盘上。每列都存储在单独的压缩文件中，写入时，数据将附加到文件末尾。
   该引擎没有并发控制
       如果同时从表中读取和写入数据，则读取操作将抛出异常；
       如果同时写入多个查询中的表，则数据将被破坏。
   这种表引擎的典型用法是 write-once：首先只写入一次数据，然后根据需要多次读取。此引擎适用于相对较小的表
（建议最多1,000,000 行)。如果有许多小表，则使用此表引擎是适合的，因为它需要打开的文件更少。当拥有大量
小表时，可能会导致性能低下。 不支持索引。
   案例：创建一个TinyLog引擎的表并插入一条数据
:)create table t (a UInt16, b String) ENGINE=TinyLog;
:)insert into t (a, b) values (1, 'abc');
   此时我们到保存数据的目录/var/lib/clickhouse/data/default/t中可以看到如下目录结构：
[root@linux121]# ls
   a.bin b.bin sizes.json
   a.bin 和 b.bin 是压缩过的对应的列的数据， sizes.json 中记录了每个 *.bin 文件的大小：
[root@linux121 ]# cat sizes.json
{"yandex":{"a%2Ebin":{"size":"28"},"b%2Ebin":{"size":"30"}}}   
   2).Log
   Log与 TinyLog 的不同之处在于，«标记» 的小文件与列文件存在一起。这些标记写在每个数据块上，并且包含偏移
量，这些偏移量指示从哪里开始读取文件以便跳过指定的行数。这使得可以在多个线程中读取表数据。对于并发数据
访问，可以同时执行读取操作，而写入操作则阻塞读取和其它写入。Log 引擎不支持索引。同样，如果写入表失败，
则该表将被破坏，并且从该表读取将返回错误。Log 引擎适用于临时数据，write-once 表以及测试或演示目的。
   3).StripeLog
   该引擎属于日志引擎系列。请在日志引擎系列文章中（https://clickhouse.tech/docs/zh/engines/table-engines/log
-family/）查看引擎的共同属性和差异。
   在你需要写入许多小数据量（小于一百万行）的表的场景下使用这个引擎。
   建表
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
column1_name [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
column2_name [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = StripeLog
   查看建表请求的详细说明。(https://clickhouse.tech/docs/zh/engines/table-engines/log-family/stripelog/#create-
table-query)
   写数据
   StripeLog 引擎将所有列存储在一个文件中。对每一次 Insert 请求，ClickHouse 将数据块追加在表文件的末尾，逐
列写入。
   ClickHouse 为每张表写入以下文件：
        data.bin — 数据文件。
        index.mrk — 带标记的文件。标记包含了已插入的每个数据块中每列的偏移量。
   StripeLog 引擎不支持 ALTER UPDATE 和 ALTER DELETE 操作。
   读数据
   带标记的文件使得 ClickHouse 可以并行的读取数据。这意味着 SELECT 请求返回行的顺序是不可预测的。使用
ORDER BY 子句对行进行排序。
   使用示例
   建表：
CREATE TABLE stripe_log_table
(
timestamp DateTime,
message_type String,
message String
)
ENGINE = StripeLog

   插入数据：
INSERT INTO stripe_log_table VALUES (now(),'REGULAR','The first regular message')
INSERT INTO stripe_log_table VALUES (now(),'REGULAR','The second regular message'),
(now(),'WARNING','The first warning message')
   我们使用两次 INSERT 请求从而在 data.bin 文件中创建两个数据块。
   ClickHouse 在查询数据时使用多线程。每个线程读取单独的数据块并在完成后独立的返回结果行。这样的结果是，
大多数情况下，输出中块的顺序和输入时相应块的顺序是不同的。例如：
SELECT * FROM stripe_log_table
┌───────────timestamp─┬─message_type─┬─message────────────────────┐
│ 2019-01-18 14:27:32 │ REGULAR │ The second regular message │
│ 2019-01-18 14:34:53 │ WARNING │ The first warning message │
└─────────────────────┴──────────────┴────────────────────────────┘
┌───────────timestamp─┬─message_type─┬─message───────────────────┐
│ 2019-01-18 14:23:43 │ REGULAR │ The first regular message │
└─────────────────────┴──────────────┴───────────────────────────┘   

   对结果排序（默认增序）：
SELECT * FROM stripe_log_table ORDER BY timestamp
┌───────────timestamp─┬─message_type─┬─message────────────────────┐
│ 2019-01-18 14:23:43 │ REGULAR │ The first regular message │
│ 2019-01-18 14:27:32 │ REGULAR │ The second regular message │
│ 2019-01-18 14:34:53 │ WARNING │ The first warning message │
└─────────────────────┴──────────────┴────────────────────────────┘

 2.Memory
   
   内存引擎，数据以未压缩的原始形式直接保存在内存当中，服务器重启数据就会消失。读写操作不会相互阻塞，不支
持索引。简单查询下有非常非常高的性能表现（超过10G/s）。
   一般用到它的地方不多，除了用来测试，就是在需要非常高的性能，同时数据量又不太大（上限大概 1 亿行）的场
景。

 3.Merge
   
   Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存储数据，但可用于同时从任意多个其他的表中读取数据。 读是自
动并行的，不支持写入。读取时，那些被真正读取到数据的表的索引（如果有的话）会被使用。
   Merge 引擎的参数：一个数据库名和一个用于匹配表名的正则表达式。
   案例：先建t1，t2，t3三个表，然后用 Merge 引擎的 t 表再把它们链接起来。
:)create table a1 (id UInt16, name String) ENGINE=TinyLog;
:)create table a2 (id UInt16, name String) ENGINE=TinyLog;
:)create table a3 (id UInt16, name String) ENGINE=TinyLog;

:)insert into a1(id, name) values (1, 'lucas');
:)insert into a2(id, name) values (2, 'lucas2');
:)insert into a3(id, name) values (3, 'lucas3');

:)create table a (id UInt16, name String) ENGINE=Merge(currentDatabase(), '^a');

:) select * from a;
┌─id─┬─name──┐
│  1 │ lucas │
└────┴───────┘
┌─id─┬─name───┐
│  2 │ lucas2 │
└────┴────────┘
┌─id─┬─name───┐
│  3 │ lucas3 │
└────┴────────┘

 4.MergeTree
   
   Clickhouse 中最强大的表引擎当属 MergeTree （合并树）引擎及该系列（*MergeTree）中的其他引擎。
   MergeTree 引擎系列的基本理念如下。当你有巨量数据要插入到表中，你要高效地一批批写入数据片段，并希望这
些数据片段在后台按照一定规则合并。相比在插入时不断修改（重写）数据进存储，这种策略会高效很多。
   1).MergeTree的创建方式与存储结构
   (1). MergeTree的创建方式：
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
...
INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
[SETTINGS name=value, ...]
   案例：
create table mt_table(date Date,id UInt8,name String) engine = MergeTree partition by
toYYYYMM(date) order by id;
CREATE TABLE mt_table3
(
`date` Date,
`id` UInt8,
`name` String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(date)
ORDER BY id;

insert into mt_table values ('2019-05-01', 1, 'zhangsan');
insert into mt_table values ('2019-06-01', 2, 'lisi');
insert into mt_table values ('2019-05-03', 3, 'wangwu');
   
   在/var/lib/clickhouse/data/default/mt_tree下可以看到：
   ls
   20190501_20190501_2_2_0 20190503_20190503_6_6_0 20190601_20190601_4_4_0 detached
   随便进入一个目录：
   ls
   checksums.txt columns.txt date.bin date.mrk id.bin id.mrk name.bin name.mrk primary.idx
       *.bin是按列保存数据的文件
       *.mrk保存块偏移量
       primary.idx保存主键索引
   [PARTITION BY expr]
   [PRIMARY KEY expr]
   [SAMPLE BY expr]
   [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
   [SETTINGS name=value, ...]
       ENGINE - 引擎名和参数。 ENGINE = MergeTree(). MergeTree 引擎没有参数。
       PARTITION BY — 分区键 。
       要按月分区，可以使用表达式 toYYYYMM(date_column) ，这里的 date_column 是一个 Date 类型的列。这
里该分区名格式会是 "YYYYMM" 这样。
       ORDER BY — 表的排序键。必选！
       可以是一组列的元组或任意的表达式。 例如: ORDER BY (CounterID, EventDate) 。
       PRIMARY KEY - 主键，如果要设成 跟排序键不相同。
       默认情况下主键跟排序键（由 ORDER BY 子句指定）相同。 因此，大部分情况下不需要再专门指定一个
PRIMARY KEY 子句。
       SAMPLE BY — 用于抽样的表达式。
       SETTINGS — 影响 MergeTree 性能的额外参数：
           index_granularity — 索引粒度。即索引中相邻『标记』间的数据行数。默认值，8192 。该列表中所有可
用的参数可以从这里查看 MergeTreeSettings.h
           index_granularity_bytes — 索引粒度，以字节为单位，默认值: 10 Mb。如果仅按数据行数限制索引粒度,
请设置为0(不建议)。
           enable_mixed_granularity_parts — 启用或禁用通过 index_granularity_bytes 控制索引粒度的大小。在
19.11 版本之前, 只有 index_granularity 配置能够用于限制索引粒度的大小。当从大表(数十或数百兆)中查
询数据时候，index_granularity_bytes 配置能够提升ClickHouse的性能。如果你的表内数据量很大，可以
开启这项配置用以提升SELECT 查询的性能。	
           use_minimalistic_part_header_in_zookeeper — 数据片段头在 ZooKeeper 中的存储方式。如果设置了
use_minimalistic_part_header_in_zookeeper=1 ，ZooKeeper 会存储更少的数据。更多信息参考『服务
配置参数』这章中的 设置描述 。
           min_merge_bytes_to_use_direct_io — 使用直接 I/O 来操作磁盘的合并操作时要求的最小数据量。合并
数据片段时,ClickHouse会计算要被合并的所有数据的总存储空间.如果大小超过了min_merge_bytes_to_use_direct_io
设置的字节数,则ClickHouse将使用直接 I/O 接口（O_DIRECT 选项）对磁盘读写。如果设置 
min_merge_bytes_to_use_direct_io = 0,则会禁用直接 I/O。默认值：10*1024*1024*1024 字节。
           merge_with_ttl_timeout — TTL合并频率的最小间隔时间。默认值: 86400 (1 天)。
           write_final_mark — 启用或禁用在数据片段尾部写入最终索引标记。默认值: 1(不建议更改)。
           storage_policy — 存储策略。 参见 使用多个区块装置进行数据存储.   
   (2).MergeTree的存储结构
-- mt_table
|-- 201905_1_1_0
| |-- checksums.txt
| |-- columns.txt
| |-- date.bin
| |-- date.mrk2
| |-- id.bin
| |-- id.mrk2
| |-- name.bin
| |-- name.mrk2
| `-- primary.idx
|-- 201906_2_2_0
| |-- checksums.txt
| |-- columns.txt
| |-- date.bin
| |-- date.mrk2
| |-- id.bin
| |-- id.mrk2
| |-- name.bin
| |-- name.mrk2
| `-- primary.idx
|-- detached
-- format_version.txt

   checksums.txt
   二进制的校验文件，保存了余下文件的大小size和size的Hash值，用于快速校验文件的完整和正确性
   columns.txt
   明文的列信息文件，如：
[root@linux121 201905_1_1_0]# cat columns.txt
columns format version: 1
3 columns:
`date` Date
`id` UInt8
`name` String  
   date.bin
   压缩格式（默认LZ4）的数据文件，保存了原始数据。以列名.bin命名。
   date.mrk2
   使用了自适应大小的索引间隔,名字为 .mrk2
   二进制的列字段标记文件，作用是把稀疏索引.idx文件和存数据的文件.bin联系起来。详见数据标记一节
   id.bin id.mrk2 name.bin name.mrk2
   primary.idx
   二进制的一级索引文件，在建表的时候通过OrderBy或者PrimaryKey声明的稀疏索引。 
   2).数据分区
   数据是以分区目录的形式组织的，每个分区独立分开存储。
   这种形式，查询数据时，可以有效的跳过无用的数据文件。
   (1).数据分区的规则
   分区键的取值，生成分区ID，分区根据ID决定。根据分区键的数据类型不同，分区ID的生成目前有四种规则：
      （1）不指定分区键
      （2）使用整形
	  （3）使用日期类型 toYYYYMM(date)
      （4）使用其他类型
   数据在写入时，会对照分区ID落入对应的分区。
   (2).分区目录的生成规则
   partitionID_MinBlockNum_MaxBlockNum_Level
   BlockNum是一个全局整型，从1开始，每当新创建一个分区目录，此数字就累加1。
   MinBlockNum:最小数据块编号
   MaxBlockNum:最大数据块编号
   对于一个新的分区，MinBlockNum和MaxBlockNum的值相同
   如：2019_05_1_1_0, 2020_05_3_3_0
   Level:合并的层级，即某个分区被合并过得次数。不是全局的，而是针对某一个分区。
   (3).分区目录的合并过程
   MergeTree的分区目录在数据写入过程中被创建。
   不同的批次写入数据属于同一分区，也会生成不同的目录，在之后的某个时刻再合并（写入后的10-15分钟），合并
后的旧分区目录默认8分钟后删除。
   同一个分区的多个目录合并以后的命名规则：
       MinBlockNum:取同一分区中MinBlockNum值最小的
       MaxBlockNum：取同一分区中MaxBlockNum值最大的
       Level:取同一分区最大的Level值加1
   3).索引
   (1).一级索引
   文件：primary.idx
   MergeTree的主键使用Primary Key定义，主键定义之后，MergeTree会根据index_granularity间隔（默认8192)为
数据生成一级索引并保存至primary.idx文件中。这种方式是稀疏索引
   简化形式：通过order by指代主键
   1、 稀疏索引
   primary.idx文件的一级索引采用稀疏索引。
   稠密索引：每一行索引标记对应一行具体的数据记录
   稀疏索引：每一行索引标记对应一段数据记录（默认索引粒度为8192）
   稀疏索引占用空间小，所以primary.idx内的索引数据常驻内存，取用速度快！
   2、 索引粒度
   index_granularity参数，表示索引粒度。新版本中clickhouse提供了自适应索引粒度。
   索引粒度在MergeTree引擎中很重要。
   3、 索引数据的生成规则
   借助hits_v1表中的真实数据观察：
   primary.idx文件
   由于稀疏索引，所以MergeTree要间隔index_granularity行数据才会生成一个索引记录，其索引值会根据声明的主键
字段获取。
   4、 索引的查询过程
   索引是如何工作的？对primary.idx文件的查询过程
   MarkRange：一小段数据区间
   按照index_granularity的间隔粒度，将一段完整的数据划分成多个小的数据段，小的数据段就是MarkRange，
MarkRange与索引编号对应。
   案例：
   共200行数据
   index_granularity大小为5
   主键ID为Int，取值从0开始
   根据索引生成规则，primary.idx文件内容为：
   0	  5	    10	   15	20	  25	30	  35	40	  45	50   ...  200
   0~4   5~9   10~14 15~19 20~24 25~29 30~34 35~39 40~44 45~49 50~54 ...194~199
   编号0 编号1 编号2 编号3 编号4
   共200行数据 / 5 = 40个MarkRange
   索引查询 where id = 3
   第一步：形成区间格式： [3,3]
   第二步：进行交集 [3,3]∩[0,199]
   以MarkRange的步长大于8分块，进行剪枝
   第三步：合并
   MarkRange:(start0,end 20)
   在ClickHouse中，MergeTree引擎表的索引列在建表时使用ORDER BY语法来指定。而在官方文档中，用了下面一幅
图来说明。
   这张图示出了以CounterID、Date两列为索引列的情况，即先以CounterID为主要关键字排序，再以Date为次要关键
字排序，最后用两列的组合作为索引键。marks与mark numbers就是索引标记，且marks之间的间隔就由建表时的
索引粒度参数index_granularity来指定，默认值为8192。
   ClickHouse MergeTree引擎表中，每个part的数据大致以下面的结构存储。
   . ├── business_area_id.bin ├── business_area_id.mrk2 ├── coupon_money.bin ├── coupon_money.mrk2
├── groupon_id.bin ├── groupon_id.mrk2 ├── is_new_order.bin ├── is_new_order.mrk2 … ├── primary.idx
… 其中，bin文件存储的是每一列的原始数据（可能被压缩存储），mrk2文件存储的是图中的mark numbers与bin
文件中数据位置的映射关系。另外，还有一个primary.idx文件存储被索引列的具体数据。另外，每个part的数据都
存储在单独的目录中，目录名形如20200708_92_121_7，即包含了分区键、起始mark number和结束mark
number，方便定位。
   在ClickHouse之父Alexey Milovidov分享的PPT中，有更加详细的图示。
   这样，每一列都通过ORDER BY列进行了索引。查询时，先查找到数据所在的parts，再通过mrk2文件确定bin文件中
数据的范围即可。
   不过，ClickHouse的稀疏索引与Kafka的稀疏索引不同，可以由用户自由组合多列，因此也要格外注意不要加入太多
索引列，防止索引数据过于稀疏，增大存储和查找成本。另外，基数太小（即区分度太低）的列不适合做索引列，因
为很可能横跨多个mark的值仍然相同，没有索引的意义了。
   (2).跳数索引
   1 、granularity和index_granularity的关系
   index_granularity定义了数据的粒度 granularity定义了聚合信息汇总的粒度 换言之,granularity定义了一行跳数索
引能够跳过多少个index_granularity区间的数据
   2 、索引的可用类型
   minmax 存储指定表达式的极值（如果表达式是 tuple ，则存储 tuple 中每个元素的极值），这些信息用于跳
过数据块，类似主键。
   set(max_rows) 存储指定表达式的惟一值（不超过 max_rows 个，max_rows=0 则表示『无限制』）。这些信
息可用于检查 WHERE 表达式是否满足某个数据块。
   ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed) 存储包含数据块
中所有 n 元短语的 布隆过滤器 。只可用在字符串上。 可用于优化 equals,like 和 in 表达式的性能。 n – 短
语长度。 size_of_bloom_filter_in_bytes – 布隆过滤器大小，单位字节。（因为压缩得好，可以指定比较大的
值，如256或512）。 number_of_hash_functions – 布隆过滤器中使用的 hash 函数的个数。 random_seed –
hash 函数的随机种子。
   tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed) 跟 ngrambf_v1 类
似，不同于 ngrams 存储字符串指定长度的所有片段。它只存储被非字母数据字符分割的片段。

INDEX sample_index (u64 * length(s)) TYPE minmax GRANULARITY 4
INDEX sample_index2 (u64 * length(str), i32 + f64 * 100, date, str) TYPE set(100)
GRANULARITY 4
INDEX sample_index3 (lower(str), str) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 4      
   4).数据存储
   表由按主键排序的数据 片段 组成。
   当数据被插入到表中时，会分成数据片段并按主键的字典序排序。例如，主键是 (CounterID, Date) 时，片段中数据
按 CounterID 排序，具有相同 CounterID 的部分按 Date 排序。
   不同分区的数据会被分成不同的片段，ClickHouse 在后台合并数据片段以便更高效存储。不会合并来自不同分区的
数据片段。这个合并机制并不保证相同主键的所有行都会合并到同一个数据片段中。
   ClickHouse 会为每个数据片段创建一个索引文件，索引文件包含每个索引行（『标记』）的主键值。索引行号定义
为 n * index_granularity 。最大的 n 等于总行数除以 index_granularity 的值的整数部分。对于每列，跟主键相同的
索引行处也会写入『标记』。这些『标记』让你可以直接找到数据所在的列。
   你可以只用一单一大表并不断地一块块往里面加入数据 – MergeTree 引擎的就是为了这样的场景
   (1).按列存储
   在MergeTree中数据按列存储，具体到每个列字段，都拥有一个.bin数据文件，是最终存储数据的文件。 按列存储的
   好处： 1、 更好的压缩 2、 最小化数据扫描范围
   MergeTree往.bin文件存数据的步骤：
   1 、对数据进行压缩
   2 、根据OrderBy排序
   3 、数据以压缩数据块的形式写入.bin文件
   (2).压缩数据块
   CompressionMethod_CompressedSize_UnccompressedSize
   一个压缩数据块有两部分组成：
   1 、头信息 2 、压缩数据
   头信息固定使用9位字节表示，1个UInt8（1字节）+2 个UInt32(4 字节)，分别表示压缩算法、压缩后数据大小、压缩
前数据大小
   如：0x821200065536
   0x82:是压缩方法
   12000:压缩后数据大小
   65536:压缩前数据大小
   clickhouse-compressor --stat命令
[root@linux121 20180301_20180330_1_100_20]# clickhouse-compressor --stat <./date.bin > out.log
[root@linux121 20180301_20180330_1_100_20]# cat out.log
200 207
[root@linux121 20180301_20180330_1_100_20]#   
   out.log文件中显示的数据压缩前数据大小，后面是压缩后数据大小。
   [Column].bin:   
   如果按照默认8192的索引粒度把数据分成批次，每批次读入数据的规则：
   设x为批次数据的大小，
   如果单批次获取的数据 x<64k,则继续读下一个批次，找到size>64k 则生成下一个数据块
   如果单批次数据64k<x<1M 则直接生成下一个数据块
   如果x>1M,则按照1M切分数据，剩下的数据继续按照上述规则执行。
   5).数据标记
   .mrk文件
   将以及索引primary.idx和数据文件.bin建立映射关系
   通用用hits_v1表说明：
   1、 数据标记和索引区间是对齐的，根据索引区间的下标编号，就能找到数据标记---索引编号和数据标记数值相同
   2、 每一个[Column].bin都有一个[Column].mrk与之对应---.mrk文件记录数据在.bin文件中的偏移量
拿JavaEnable字段说明：
   1 b * 8192 = 8192 b 8192 b * 8 = 64 k
   (1). .mrk文件内容的生成规则
   数据标记和区间是对齐的。均按照index_granularity粒度间隔。可以通过索引区间的下标编号找到对应的数据标记。
   每一个列字段的.bin文件都有一个.mrk数据标记文件，用于记录数据在.bin文件中的偏移量信息。
   标记数据采用LRU缓存策略加快其取用速度
   (2). .mrk文件的工作方式
   6).分区、索引、标记和压缩协同
   (1).写入过程
   1 、生成分区目录
   2 、合并分区目录
   3 、生成primary.idx索引文件、每一列的.bin和.mrk文件
   (2).查询过程
   1 、根据分区索引缩小查询范围
   2 、根据数据标记，缩小查询范围
   3 、解压压缩块
   (3).数据标记与压缩数据块的对应关系
   多对一：
   一对一：
   一对多：
   7).MergTree的TTL
   TTL：time to live 数据存活时间。TTL既可以设置在表上，也可以设置在列上。TTL指定的时间到期后则删除相应的
表或列，如果同时设置了TTL，则根据先过期时间删除相应数据。
   用法：
   TTL time_col + INTERVAL 3 DAY
   表示数据存活时间是time_col时间的3天后
   INTERVAL可以设定的时间：SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR
   (1).TTL设置在列上
   例：
create table ttl_table_v1 (
id String,
create_time DateTime,
code String TTL create_time + INTERVAL 10 SECOND,
type UInt8 TTL create_time + INTERVAL 10 SECOND
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY id;

insert into ttl_table_v1 values('1',now(),'A000',1),('A000',now()+INTERVAL 10
MINUTE,'C1',1);

SELECT *
FROM ttl_table_v1;

┌─id───┬─────────create_time─┬─code─┬─type─┐
│ A000 │ 2020-08-19 20:11:51 │ C1 │ 1 │
│ A000 │ 2020-08-19 20:21:51 │ C1 │ 1 │
└──────┴─────────────────────┴──────┴──────┘

optimize table ttl_table_v1 FINAL;

SELECT *
FROM ttl_table_v1;
┌─id───┬─────────create_time─┬─code─┬─type─┐
│ A000 │ 2020-08-19 20:11:51 │ │ 0 │
│ A000 │ 2020-08-19 20:21:51 │ C1 │ 1 │
└──────┴─────────────────────┴──────┴──────┘      
   (2). TTL设置在表上
create table tt1_table_v2 (
id String,
create_time DateTime,
code String TTL create_time + INTERVAL 10 SECOND,
type UInt8
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY create_time
TTL create_time + INTERVAL 1 DAY;

ALTER TABLE tt1_table_v1 MODIFY TTL create_time + INTERVAL + 3 DAY;
   
   TTL目前没有取消方法
   (3).TTL文件说明
[root@linux121 202008_1_1_0]# cat ttl.txt
ttl format version: 1
{"columns":[{"name":"code","min":1597839121,"max":1597839721},
{"name":"type","min":1597839121,"max":1597839721}]}

SELECT
toDateTime(1597839121) AS ttl_min,
toDateTime(1597839721) AS ttl_max;

┌─────────────ttl_min─┬─────────────ttl_max─┐
│ 2020-08-19 20:12:01 │ 2020-08-19 20:22:01 │
└─────────────────────┴─────────────────────┘

SELECT *
FROM ttl_table_v1;
┌─id───┬─────────create_time─┬─code─┬─type─┐
│ A000 │ 2020-08-19 20:11:51 │ │ 0 │
│ A000 │ 2020-08-19 20:12:01 │ C1 │ 1 │
└──────┴─────────────────────┴──────┴──────┘

   一列数据中最小时间：2020-08-19 20:11:51 + INTERVAL的10秒钟 = 2020-08-19 20:12:01 时间
戳："min":1597839121
   一列数据中最大时间：2020-08-19 20:12:01 + INTERVAL的10秒钟 = 2020-08-19 20:22:01 时间
戳："max":1597839721
   文件ttl.txt记录的是列字段的过期时间。
   8).MergeTree的存储策略
   整体配置：
<storage_configuration>
	<disks>
		<disk_hot1>
			<path>/var/lib/clickhouse/chbase/hotdata1/</path>
		</disk_hot1>
		<disk_hot2>
			<path>/var/lib/clickhouse/chbase/hotdata2/</path>
		</disk_hot2>
		<disk_cold>
			<path>/var/lib/clickhouse/chbase/colddata/</path>
		</disk_cold>
	</disks>
	<policies>
		<default_jbod>
			<volumes>
				<jbod>
					<disk>disk_hot1</disk>:
					<disk>disk_hot2</disk>
				</jbod>
			</volumes>
			<move_factor>0.2</move_factor>
		</default_jbod>
		<moving_from_hot_to_cold>
			<volumes>
				<hot>
					<disk>disk_hot1</disk>
					<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
				</hot>
				<cold>
					<disk>disk_cold</disk>
				</cold>
			</volumes>
			<move_factor>0.2</move_factor>
		</moving_from_hot_to_cold>
		<moving_from_hot_to_cold_new>
			<volumes>
				<hot>
					<disk>disk_hot2</disk>
					<max_data_part_size_bytes>1048576</max_data_part_size_bytes>
				</hot>
				<cold>
					<disk>disk_cold</disk>
				</cold>
			</volumes>
			<move_factor>0.2</move_factor>
		</moving_from_hot_to_cold_new>
	</policies>
</storage_configuration>

   1).默认策略
   19.15 之前,只能单路径存储，存储位置为在config.xml配置文件中指定
	<!-- Path to data directory, with trailing slash. -->
    <path>/var/lib/clickhouse/</path>
   19.15 之后，支持多路径存储策略的自定义存储策略，目前有三类策略：
   2).JBOD策略
   配置方式在config.xml配置文件中指定：
<storage_configuration>
	<disks>
		<disk_hot1>
			<path>/var/lib/clickhouse/chbase/hotdata1/</path>
		</disk_hot1>
		<disk_hot2>
			<path>/var/lib/clickhouse/chbase/hotdata2/</path>
		</disk_hot2>
		<disk_cold>
			<path>/var/lib/clickhouse/chbase/colddata/</path>
		</disk_cold>
	</disks>
	<policies>
		<default_jbod>
			<volumes>
				<jbod>
					<disk>disk_hot1</disk>:
					<disk>disk_hot2</disk>
				</jbod>
			</volumes>
			<move_factor>0.2</move_factor>
		</default_jbod>
	</policies>
</storage_configuration>

   service clickhouse-server restart
select
name,
path,
formatReadableSize(free_space) as free,
formatReadableSize(total_space) as total,
formatReadableSize(keep_free_space) as reserved
from system.disks;

SELECT
name,
path,
formatReadableSize(free_space) AS free,
formatReadableSize(total_space) AS total,
formatReadableSize(keep_free_space) AS reserved
FROM system.disks;

─name──────┬─path─────────────────────────────────┬─free─────┬─total─────┬─reserved─┐
│ default │ /var/lib/clickhouse/ │ 5.02 GiB │ 16.99 GiB │ 0.00 B │
│ disk_cold │ /var/lib/clickhouse/chbase/colddata/ │ 5.02 GiB │ 16.99 GiB │ 0.00 B │
│ disk_hot1 │ /var/lib/clickhouse/chbase/hotdata1/ │ 5.02 GiB │ 16.99 GiB │ 0.00 B │
│ disk_hot2 │ /var/lib/clickhouse/chbase/hotdata2/ │ 5.02 GiB │ 16.99 GiB │ 0.00 B │
└───────────┴──────────────────────────────────────┴──────────┴───────────┴──────────┘

   system.disk系统表，刚才配置的三块磁盘已经生效
select
policy_name,
volume_name,
volume_priority,
disks,
formatReadableSize(max_data_part_size) max_data_part_size,
move_factor
from system.storage_policies;

SELECT
policy_name,
volume_name,
volume_priority,
disks,
formatReadableSize(max_data_part_size) AS max_data_part_size,
move_factor
FROM system.storage_policies;

┌─policy_name──┬─volume_name─┬──disks─────────────────────┬─move_factor─┐
│ default │ default │ ['default'] │ 0 │
│ default_jbod │ jbod │ ['disk_hot1','disk_hot2'] │ 0.2 │
└──────────────┴─────────────┴────────────────────────────┴─────────────┘
   
   system.storage_policies系统表可以看到刚才配置的策略也生效了。
   create table jbob_table(id UInt64) engine=MergeTree
order by id settings storage_policy='default_jbod';
insert into table jbod_table select rand() from numbers(10);
select name,disk_name from system.parts where table='jbod_table';\

SELECT
name,
disk_name
FROM system.parts
WHERE table = 'jbod_table';

┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
└───────────┴───────────┘
   在此插入一条数据：
insert into jbod_table select rand() from numbers(10);
select name,disk_name from system.parts where table='jbod_table';

SELECT
name,
disk_name
FROM system.parts
WHERE table = 'jbob_table';

┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_2_2_0 │ disk_hot2 │
└───────────┴───────────┘   
   触发合并操作：
optimize table jbob_table;
SELECT
name,
disk_name
FROM system.parts
WHERE table = 'jbob_table';

┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_1_2_1 │ disk_hot1 │
│ all_2_2_0 │ disk_hot2 │
└───────────┴───────────┘   
   验证JBOD策略的工作方式，多个磁盘组成了一个磁盘组volume卷，新生成的数据分区，分区目录会按照volume卷
中的磁盘定义顺序，轮询写入数据。
   3).HOT/COLD策略
   conf.xml配置文件：
<storage_configuration>
	<disks>
		<disk_hot1>
			<path>/var/lib/clickhouse/chbase/hotdata1/</path>
		</disk_hot1>
		<disk_hot2>
			<path>/var/lib/clickhouse/chbase/hotdata2/</path>
		</disk_hot2>
		<disk_cold>
			<path>/var/lib/clickhouse/chbase/colddata/</path>
		</disk_cold>
	</disks>
	<policies>
		<default_jbod>
			<volumes>
				<jbod>
					<disk>disk_hot1</disk>:
					<disk>disk_hot2</disk>
				</jbod>
			</volumes>
			<move_factor>0.2</move_factor>
		</default_jbod>
		<moving_from_hot_to_cold>
			<volumes>
				<hot>
					<disk>disk_hot1</disk>
					<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
				</hot>
				<cold>
					<disk>disk_cold</disk>
				</cold>
			</volumes>
			<move_factor>0.2</move_factor>
		</moving_from_hot_to_cold>
	</policies>
</storage_configuration>

   重启服务：service clickhouse-server restart
   创建表，测试moving_from_hot_to_cold存储策略：
create table hot_cold_table(
id UInt64
)ENGINE=MergeTree()
ORDER BY id
SETTINGS storage_policy='moving_from_hot_to_cold_new';
   写入一批500K的数据，生成一个分区目录：
insert into hot_cold_table select rand() from numbers(100000);
select name,disk_name from system.parts where table = 'hot_cold_table';

SELECT
name,
disk_name
FROM system.parts;

WHERE table = 'hot_cold_table'
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
└───────────┴───────────┘  

   第一个分区写入hot1卷。
   接着写入第二批500k的数据 
insert into hot_cold_table select rand() from numbers(100000);
select name,disk_name from system.parts where table = 'hot_cold_table';

SELECT
name,
disk_name
FROM system.parts
WHERE table = 'hot_cold_table'
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_2_2_0 │ disk_hot1 │
└───────────┴───────────┘

   每一个分区大小为500k
   接下来触发合并：
OPTIMIZE TABLE hot_cold_table;
Ok.
0 rows in set. Elapsed: 0.013 sec.
hdp-1 :) select name, disk_name from system.parts where table = 'hot_cold_table_new';
SELECT
name,
disk_name
FROM system.parts
WHERE table = 'hot_cold_table_new';

┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk_hot2 │
│ all_1_2_1 │ disk_cold │
│ all_2_2_0 │ disk_hot2 │
└───────────┴───────────┘

   查询大小：
select
disk_name,
formatReadableSize(bytes_on_disk) as size
from system.parts where (table = 'hot_cold_table') and active = 1;

SELECT
disk_name,
formatReadableSize(bytes_on_disk) AS size
FROM system.parts
WHERE (table = 'hot_cold_table_new') AND (active = 1);

┌─disk_name─┬─size─────┐
│ disk_cold │ 1.01 MiB │
└───────────┴──────────┘ 

   虽然MergeTree的存储策略不能修改，但是分区目录却支持移动。  
   